package com.lsm.searchservice.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.hmall.common.pojo.dto.PageDTO;
import com.lsm.searchservice.pojo.ItemDoc;
import com.lsm.searchservice.pojo.dto.FiltersDto;
import com.lsm.searchservice.service.SearchService;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggester;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SearchServiceImpl implements SearchService {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Value("${es.indexName}")
    private String indexName;

    /**
     * 根据搜索条件搜索文档，并对文档中的分类、品牌做聚合，返回聚合得到的分类、品牌信息
     *
     * @param filtersDto
     * @return
     */
    @Override
    public Map<String, List<String>> filters(FiltersDto filtersDto) {
        String key = filtersDto.getKey();
        String category = filtersDto.getCategory();
        String brand = filtersDto.getBrand();
        Long minPrice = filtersDto.getMinPrice();
        Long maxPrice = filtersDto.getMaxPrice();

        SearchRequest req = new SearchRequest(indexName);
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // 没有key全查，有key根据name字段分词查
        if (null == key || "".equals(key))
            boolQueryBuilder.filter(QueryBuilders.matchAllQuery());
        else
            boolQueryBuilder.must(QueryBuilders.matchQuery("name", key));

        // 价格
        if (null != minPrice)
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(minPrice));

        if (null != maxPrice)
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(maxPrice));

        req.source().query(boolQueryBuilder);

        //查询字段不需要分组
        String categoryGroup = "categoryGroup";
        String brandGroup = "brandGroup";

        if (null == brand || "".equals(brand)) {
            TermsAggregationBuilder aggs = AggregationBuilders.terms(brandGroup).field("brand").size(15);
            req.source().aggregation(aggs);
        } else
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", brand));

        if (null == category || "".equals(category)) {
            TermsAggregationBuilder aggs = AggregationBuilders.terms(categoryGroup).field("category").size(15);
            req.source().aggregation(aggs);
        } else
            boolQueryBuilder.filter(QueryBuilders.termQuery("category", category));

        SearchResponse resp = null;
        try {
            resp = restHighLevelClient.search(req, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //解析数据
        Aggregations aggregations = resp.getAggregations();

        if(null == aggregations)
            return new HashMap<>();

        Map<String, Aggregation> asMap = aggregations.asMap();

        ParsedTerms p  = aggregations.get("");

        return asMap.entrySet().stream().collect(Collectors.toMap(
                stringAggregationEntry -> {
                    if (stringAggregationEntry.getKey().equals(categoryGroup))
                        return "category";
                    else
                        return "brand";
                },
                stringAggregationEntry -> {
                    ParsedTerms parsedTerms = (ParsedTerms) stringAggregationEntry.getValue();
                    List<? extends Terms.Bucket> buckets = parsedTerms.getBuckets();
                    return buckets.stream().map(b-> (String)b.getKey()).collect(Collectors.toList());
                }
        ));
    }

    @Override
    public PageDTO<ItemDoc> list(FiltersDto filtersDto) {
        String key = filtersDto.getKey();
        String sort = filtersDto.getSortBy();
        String category = filtersDto.getCategory();
        String brand = filtersDto.getBrand();
        Long minPrice = filtersDto.getMinPrice();
        Long maxPrice = filtersDto.getMaxPrice();
        Integer page = filtersDto.getPage();
        Integer size = filtersDto.getSize();

        SearchRequest req = new SearchRequest(indexName);
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // 没有key全查，有key根据name字段分词查
        if (null == key || "".equals(key))
            boolQueryBuilder.filter(QueryBuilders.matchAllQuery());
        else {
            boolQueryBuilder.must(QueryBuilders.matchQuery("name", key));

            // 高亮
            req.source().highlighter(new HighlightBuilder().field("name").preTags("<em>").postTags("</em>"));
        }

        // 品牌
        if (null != brand && !"".equals(brand))
            boolQueryBuilder.filter(QueryBuilders.termQuery("brand", brand));

        // 分类
        if (null != category && !"".equals(category))
            boolQueryBuilder.filter(QueryBuilders.termQuery("category", category));

        // 价格
        if (null != minPrice)
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(minPrice));

        if (null != maxPrice)
            boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(maxPrice));

        // 竞价排名
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                boolQueryBuilder,
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                QueryBuilders.termQuery("isAD", true),
                                ScoreFunctionBuilders.weightFactorFunction(5)
                        )
                }).boostMode(CombineFunction.REPLACE);

        req.source().query(functionScoreQuery).from((page - 1) * size).size(size);

        // 排序
        if (null != sort && !"".equals(sort) && !"default".equals(sort))
            req.source().sort(sort);

        SearchResponse resp = null;
        try {
            resp = restHighLevelClient.search(req, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 解析数据
        SearchHits hits = resp.getHits();

        return PageDTO.<ItemDoc>builder()
                .total(hits.getTotalHits().value)
                .list(
                        Arrays.stream(hits.getHits())
                                .map(hit -> {
                                    ItemDoc itemDoc = JSONObject.parseObject(hit.getSourceAsString(), ItemDoc.class);
                                    if (null != key && !"".equals(key))
                                        itemDoc.setName(Arrays.toString(hit.getHighlightFields().get("name").getFragments()));

                                    return itemDoc;
                                })
                                .collect(Collectors.toList())
                )
                .build();
    }

    /**
     * 搜索框自动补全
     * @param key
     * @return
     */
    @Override
    public List<String> suggestion(String key) {
        String group = "suggestion";
        SearchRequest req = new SearchRequest("completion");
        req.source().suggest(
                new SuggestBuilder().addSuggestion(
                        group,
                        SuggestBuilders
                                .completionSuggestion("word")
                                .prefix(key)
                                .skipDuplicates(true)
                                .size(10)
                )
        );

        SearchResponse search = null;
        try {
            search = restHighLevelClient.search(req, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (search == null)
            return null;

        Suggest suggest = search.getSuggest();
        Suggest.Suggestion<? extends Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option>> suggestion = suggest.getSuggestion(group);

        ArrayList<String> list = new ArrayList<>();

        for (Suggest.Suggestion.Entry<? extends Suggest.Suggestion.Entry.Option> options : suggestion) {
            for (Suggest.Suggestion.Entry.Option option : options) {
                list.add(String.valueOf(option.getText()));
            }
        }

        return list;
    }
}
